package org.datascooter.utils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Logger;

import javax.persistence.Transient;

import org.datascooter.bundle.CrossData;
import org.datascooter.utils.policy.ParamClass;

public class TypeUtils {

	public static final String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";

	public static String formatDate(Date date) {
		return DSSettings.getFormat().format(date);
	}

	public static void main(String[] args) {
		try {
			Date parseDate = parseDate("2013-02-11 09:26:01.0");
			System.out.println("------------ok----------" + parseDate);
		} catch (ParseException e) {
			e.printStackTrace();
		}
	}

	public static Date parseDate(String date) throws ParseException {
		if (date.length() == 0 || date.trim().length() == 0) {
			return null;
		}
		try {
			if (date.endsWith(".0")) {// 2013-02-11 09:26:01.0
				date = date.substring(0, date.length() - 2);
			}
			return DSSettings.getFormat().parse(date);
		} catch (Exception e) {
			Logger.getLogger(TypeUtils.class.getName()).severe("***Wrong default date format? -- Can not convert : " + date);
		}
		return null;
	}

	public static void copy(Object existsObject, Object obj) throws IllegalArgumentException, IllegalAccessException {
		List<Field> exploreFields = exploreFields(obj.getClass());
		for (Field field : exploreFields) {
			field.set(existsObject, field.get(obj));
		}
	}

	public static List<Field> exploreFields(Class<?> clazz) {
		List<Field> result = new ArrayList<Field>();
		exploreFields(result, clazz);
		return result;
	}

	public static List<Field> exploreFields(Class<?> clazz, Class<? extends Annotation> annotationClass) {
		List<Field> result = new ArrayList<Field>();
		exploreFields(result, clazz, annotationClass);
		return result;
	}

	public static List<Method> exploreMethods(Class<?> clazz, Class<? extends Annotation> annotationClass) {
		List<Method> result = new ArrayList<Method>();
		exploreMethods(result, clazz, annotationClass);
		return result;
	}

	public static void exploreFields(List<Field> list, Class<?> clazz, Class<? extends Annotation> annotationClass) {
		list.addAll(Arrays.asList(clazz.getDeclaredFields()));
		Iterator<Field> iterator = list.iterator();
		while (iterator.hasNext()) {
			Field next = iterator.next();
			if (next.getAnnotation(annotationClass) == null) {
				iterator.remove();
			} else {
				next.setAccessible(true);
			}
		}
		if (clazz.getSuperclass() != null) {
			exploreFields(list, clazz.getSuperclass(), annotationClass);
		}
	}

	public static void exploreFields(List<Field> list, Class<?> clazz) {
		list.addAll(Arrays.asList(clazz.getDeclaredFields()));
		Iterator<Field> iterator = list.iterator();
		while (iterator.hasNext()) {
			Field next = iterator.next();
			if (Modifier.isTransient(next.getModifiers()) || next.getAnnotation(Transient.class) != null || Modifier.isStatic(next.getModifiers())) {
				iterator.remove();
			} else {
				next.setAccessible(true);
			}
		}
		if (clazz.getSuperclass() != null) {
			exploreFields(list, clazz.getSuperclass());
		}
	}

	public static void exploreMethods(List<Method> list, Class<?> clazz, Class<? extends Annotation> annotationClass) {
		list.addAll(Arrays.asList(clazz.getDeclaredMethods()));
		Iterator<Method> iterator = list.iterator();
		while (iterator.hasNext()) {
			Method next = iterator.next();
			if (next.getAnnotation(annotationClass) == null) {
				iterator.remove();
			} else {
				next.setAccessible(true);
			}
		}
		if (clazz.getSuperclass() != null) {
			exploreMethods(list, clazz.getSuperclass(), annotationClass);
		}
	}

	public static DBType getTypeByValue(Object value) {
		if (value != null) {
			Class<?> klass = (Class<?>) (value instanceof Class ? value : value.getClass());
			if (klass.equals(String.class)) {
				if (value.toString().length() > 4000) {
					return DBType.CLOB;
				}
				return DBType.STRING;
			} else if (klass.equals(Integer.class)) {
				return DBType.INT;
			} else if (klass.equals(Long.class)) {
				return DBType.LONG;
			} else if (klass.equals(Character.class)) {
				return DBType.CHAR;
			} else if (klass.equals(Boolean.class)) {
				return DBType.BOOLEAN;
			} else if (klass.equals(BigDecimal.class)) {
				return DBType.BIGDECIMAL;
			} else if (klass.equals(Date.class)) {
				return DBType.TIMESTAMP;
			} else if (klass.equals(Double.class)) {
				return DBType.BIGDECIMAL;
			} else if (klass.equals(Float.class)) {
				return DBType.BIGDECIMAL;
			} else if (klass.equals(BigInteger.class)) {
				return DBType.LONG;
			} else if (klass.equals(Byte.class)) {
				return DBType.BLOB;
			} else if (klass.equals(int.class)) {
				return DBType.INT;
			} else if (klass.equals(double.class)) {
				return DBType.BIGDECIMAL;
			} else if (klass.equals(float.class)) {
				return DBType.BIGDECIMAL;
			} else if (klass.equals(boolean.class)) {
				return DBType.BOOLEAN;
			} else if (klass.equals(long.class)) {
				return DBType.LONG;
			} else if (klass.equals(char.class)) {
				return DBType.CHAR;
			} else if (klass.equals(byte.class)) {
				return DBType.BOOLEAN;
			} else if (klass.equals(byte[].class)) {
				return DBType.BLOB;
			} else if (klass.equals(char[].class)) {
				return DBType.CLOB;
			} else if (klass.isEnum()) {
				return DBType.STRING;
			}
		}
		return DBType.STRING;
	}

	public static Object getTypeInstance(ParamClass paramClass, Object obj) throws ParseException {
		if (obj == null) {
			return obj;
		}
		switch (paramClass) {
			case ds_String:
				return obj.toString();
			case ds_Integer:
				return Integer.valueOf(obj.toString());
			case ds_Long:
				return Long.valueOf(obj.toString());
			case ds_Character:
				return obj.toString().charAt(0);
			case ds_Boolean:
				return extracted(obj.toString());
			case ds_boolean:
				return extracted(obj.toString()).booleanValue();
			case ds_BigDecimal:
				return new BigDecimal(obj.toString());
			case ds_Date:
				return parseDate(obj.toString());
			case ds_Double:
				return Double.valueOf(obj.toString());
			case ds_Float:
				return Float.valueOf(obj.toString());
			case ds_BigInteger:
				return new BigInteger(obj.toString());
			case ds_Byte:
				return obj.toString().getBytes();
			case ds_int:
				return Integer.valueOf(obj.toString()).intValue();
			case ds_double:
				return Double.valueOf(obj.toString()).doubleValue();
			case ds_float:
				return Float.valueOf(obj.toString()).floatValue();
			case ds_long:
				return Long.valueOf(obj.toString()).longValue();
			case ds_char:
				return obj.toString().charAt(0);
			case ds_array:
				return obj.toString().getBytes();
			case ds_bytes:
				return obj.toString().getBytes();
			default:
				return obj;
		}
	}

	public static ParamClass getParamClass(Class<?> klass) {
		if (klass.equals(String.class)) {
			return ParamClass.ds_String;
		} else if (klass.equals(Integer.class)) {
			return ParamClass.ds_Integer;
		} else if (klass.equals(Long.class)) {
			return ParamClass.ds_Long;
		} else if (klass.equals(Character.class)) {
			return ParamClass.ds_Character;
		} else if (klass.equals(Boolean.class)) {
			return ParamClass.ds_Boolean;
		} else if (klass.equals(BigDecimal.class)) {
			return ParamClass.ds_BigDecimal;
		} else if (klass.equals(Date.class)) {
			return ParamClass.ds_Date;
		} else if (klass.equals(Double.class)) {
			return ParamClass.ds_Double;
		} else if (klass.equals(Float.class)) {
			return ParamClass.ds_Float;
		} else if (klass.equals(BigInteger.class)) {
			return ParamClass.ds_BigInteger;
		} else if (klass.equals(Byte.class)) {
			return ParamClass.ds_Byte;
		} else if (klass.equals(int.class)) {
			return ParamClass.ds_int;
		} else if (klass.equals(double.class)) {
			return ParamClass.ds_double;
		} else if (klass.equals(float.class)) {
			return ParamClass.ds_float;
		} else if (klass.equals(boolean.class)) {
			return ParamClass.ds_boolean;
		} else if (klass.equals(long.class)) {
			return ParamClass.ds_long;
		} else if (klass.equals(char.class)) {
			return ParamClass.ds_char;
		} else if (klass.equals(byte.class)) {
			return ParamClass.ds_bytes;
		} else if (klass.isArray()) {
			return ParamClass.ds_array;
		} else if (klass.isEnum()) {
			return ParamClass.ds_enum;
		} else {
			return ParamClass.ds_none;
		}
	}

	private static Boolean extracted(String objectStr) {
		if (objectStr.equalsIgnoreCase("1")) {
			return Boolean.TRUE;
		} else if (objectStr.equalsIgnoreCase("0")) {
			return Boolean.FALSE;
		}
		return Boolean.valueOf(objectStr);
	}

	public static boolean isPrimitive(Class<?> klass) {
		if (klass != null) {
			if (klass.equals(String.class) || klass.equals(Integer.class) || klass.equals(Long.class) || klass.equals(Character.class) || klass.equals(Boolean.class) || klass.equals(Object.class)
				|| klass.equals(BigDecimal.class) || klass.equals(Date.class) || klass.equals(Double.class) || klass.equals(Float.class) || klass.equals(BigInteger.class) || klass.equals(Byte.class)
				|| klass.equals(int.class) || klass.equals(double.class) || klass.equals(float.class) || klass.equals(boolean.class) || klass.equals(long.class) || klass.equals(char.class)
				|| klass.equals(byte.class) || klass.equals(byte[].class) || klass.equals(char[].class) || klass.equals(void.class) || klass.isEnum() || klass.equals(CrossData.class)) {
				return true;
			}
		}
		return false;
	}

	public static int getDefaultScale(DBType type) {
		switch (type) {
			case STRING:
				return DSSettings.getDefaultStringLength();
			case CHAR:
				return DSSettings.getDefaultStringLength();
			case BIGDECIMAL:
				return 22;
			case BOOLEAN:
				return 1;
			case INT:
				return 14;
			case LONG:
				return 22;
			case BLOB:
				return DSSettings.getDefaultBlobLength();
			case CLOB:
				return DSSettings.getDefaultClobLength();
		}
		return 0;
	}

	public static int getDefaultPrecision(DBType type) {
		switch (type) {
			case STRING:
				return 0;
			case BIGDECIMAL:
				return 2;
			case BLOB:
				return 0;
			case BOOLEAN:
				return 0;
			case CLOB:
				return 0;
			case INT:
				return 0;
			case LONG:
				return 0;
			case CHAR:
				return 0;
			case TIMESTAMP:
				return 0;
		}
		return 0;
	}

	public static String getDbTypeString(DBType type) {
		switch (type) {
			case BIGDECIMAL:
				return SnipUtils.NUMBER;
			case LONG:
				return SnipUtils.NUMBER;
			case BOOLEAN:
				return SnipUtils.NUMBER;
			case INT:
				return SnipUtils.NUMBER;
			case STRING:
				return SnipUtils.VARCHAR;
			case CHAR:
				return SnipUtils.CHAR;
			case BLOB:
				return SnipUtils.BLOB;
			case CLOB:
				return SnipUtils.CLOB;
			case TIMESTAMP:
				return SnipUtils.TIMESTAMP;
		}
		return null;
	}

	public static int getDefaultScale(Class<?> clazz) {
		return getDefaultScale(getTypeByValue(clazz));
	}

	public static int getDefaultPrecision(Class<?> clazz) {
		return getDefaultPrecision(getTypeByValue(clazz));
	}

	public static boolean isDBTypeNumeric(String dataTypeName) {
		if ("DECIMAL".equalsIgnoreCase(dataTypeName)) {
			return true;
		} else if ("BIGINT".equalsIgnoreCase(dataTypeName)) {
			return true;
		} else if ("SMALLINT".equalsIgnoreCase(dataTypeName)) {
			return true;
		} else if ("TINYINT".equalsIgnoreCase(dataTypeName)) {
			return true;
		} else if ("INTEGER".equalsIgnoreCase(dataTypeName)) {
			return true;
		} else if ("FLOAT".equalsIgnoreCase(dataTypeName)) {
			return true;
		} else if ("REAL".equalsIgnoreCase(dataTypeName)) {
			return true;
		} else if ("DOUBLE".equalsIgnoreCase(dataTypeName)) {
			return true;
		} else if ("NUMERIC".equalsIgnoreCase(dataTypeName)) {
			return true;
		} else if ("int8".equalsIgnoreCase(dataTypeName)) {
			return true;
		} else if ("int4".equalsIgnoreCase(dataTypeName)) {
			return true;
		} else if ("int".equalsIgnoreCase(dataTypeName)) {
			return true;
		} else {
			return false;
		}
	}
}
